BlitImageThreadMode
BlitImageThreadMode Mode
 
Parameters:

    Mode = The Threading mode to enable for th blitimage commands
Returns: NONE
 

     BlitImageThreadMode controls the threading mode the BlitImage commands use.

      The blitimage command set are by design, big job commands, meaning they do a lot of data processing making them ideal for threading. Threading is the ability to execute a 'function' upon a different CPU core than your main program. So when we thread a blitimage function, PlayBASIC passes this task from our program onto some other CPU/Core. In systems that have more than one core, or physical cpu, we're thus executing two things at once. Systems with a single core cpu can still use threading, but the jobs are executed by the system in a 'one at a time' fashion. Depending upon the size of the threaded job, this can still be of benefit to a single core system.

      PlayBASIC currently supports two different types of threading. We have an immediate single job mode an a queue mode. We contol these using the BlitImageThreadMode MODE command.

     Threading Mode Values

           0 = Threading OFF, (default) In this mode all blitimage calls are drawn by the program immediately

           1 = Push Single Job, When this mode is enabled, the next blitimage function called will be pushed onto a second core and executed now.

           2 = Start Queue. This setting makes the blitimage function calls stack up. They're not executed until you set the Mode to Render QUEUE mode

           3 = Render Queue. Fires up the thread that renders the previously queued list of blitimage render tasks.




I N D E X:






Threading Mode #1: Single Task



      The single task mode is the easiest threading available but also a fairly limited. However once you wrap your mind around this mode understanding the queue method will be much easier. Threading can seem fairly strange initially, so we'll tackle this using a human example.

      Let's imagine if we want to dig a pair of large holes in the ground. If there's only one person "Bill" , the work load falls completely on his shovel, so if it takes an hour to dig one hole, then it'll take two hours for both. Now what if Bill asked a friend "Joe" to help dig one hole with him, then they could immediately half the time that complete task will take. Now we're assuming here that both Bill & Joe are at the location where the works needs to be done. What if Joe can't make it for an hour ? Then the benefit of the pair working together isn't as efficient.

      This is what mode #1 is all about, is about sharing a job between a pair of cpu's in your computer, which would normally be performed on a single cpu running your program. So while this could theoretically double the speed of the operation on a multi core system, we're at the mercy of how quickly the operating system can start our threaded job. It may start immediately, but the system might also be busy and it might not start our task for a number of milliseconds. When that occurs, the efficiency is decreased.

      In this example we're going to split up a full screen blitimage render to simulate two workers sharing the load.

  
  
  #Include "BlitIMage"
  
  ScreenWidth     =GetScreenWidth()
  ScreenHeight=GetScreenHeight()
  
  FXScreen          =NewImage(ScreenWidth,ScreenHeight,2)
  
  ThreadMe=true
  
  Do
     
     RenderToImage  FXScreen
     
     // Draw Some circles for something to look at
     InkMode 1+64
     x=MouseX()
     y=MouseY()
     For lp =0 To 359 Step 10
        x2=x+Cos(Angle#+lp)*100
        y2=y+Sin(Angle#+lp)*100
        CircleC x2,y2,25,true,$213111
     Next
     
     InkMode 1
     Angle#=WrapAngle(Angle#+0.25)
     
     RenderToScreen
     // Draw the FXscreen to our PB screen in video memory
     // this BlitImage routine Mults the source image with
     // the colour, so pixels slowly fade out to black
     LockBuffer
     
     // In this program we're going to thread this functions
     // execution.
     
     If TheadState
        // Enable Single Job threading mode
        blitimagethreadmode 1
     EndIf
     
     TopSection=ScreenHeight*0.75
     // Set the screen viewport to the top half of the screen
     ScreenViewPort 0,0,ScreenWidth,TopSection
     
     // Call the blit function,  This job will be pushed onto
     // another cpu/core for this top half of the screen.
     BlitImageAlphaPostMultColour(FXScreen,0,0,$e0a090)
     
     
     // Switch the thread state to off, so when we call a BlitImage
     // function, the main program renders it.
     blitimagethreadmode 0
     
     // Set the screen viewport to the bottom half of the screen
     ScreenViewPort 0,TopSection,ScreenWidth,ScreenHeight
     
     // Call the blit function again.  This bottom section of the
     // screen will be drawn by the main program.
     BlitImageAlphaPostMultColour(FXScreen,0,0,$e0a090)
     
     UnLockBuffer
     
     
     // Restore the screen viewport to full screen.
     ScreenViewPort 0,0,ScreenWidth,ScreenHeight
     
     // here we'll overlap our key reading, just in case the thread is still working
     
     If SpaceKey()
        ThreadMe=1-ThreadMe
        FlushKeys
     EndIf
     
     
     // Wait for the thread to complete it's work (if it hasn't already)
     // since we need the results of the thread
     waitonblitimagethread()
     
     // Tell PB to draw to the screen
     RenderToScreen
     
     Text 10,10,"Thread State:"+Str$(ThreadMe)
     Text 10,20,"Fps:"+Str$(FPS())
     
     Sync
  Loop
  
  


      On my test systems this approach works, but it doesn't really return much benefit, since the screen size is fairly small and BlitImage function we're using are reasonably efficient to begin with. We might see some gain on larger screen or using a less efficient render function.

      There is another approach we can take, which is to give out worker threads two unrelated tasks to perform in unison. So if we can overlap the tasks equally, we potentially double our performance when the program is under stress. This is one of the difficulties of threading computer programs, working out where what jobs are performed by what thread and trying to balance the load. Which is easier said than done.

      In this example we're going to push the BlitImage function onto our second CPU core, then while this taking place we'll render our bunch of circles. So the key difference here is that we're no longer rendering to the same version of the screen, rather we're now manually doubling buffering. So the blitimage function is drawing (and modifying in this case) the previous frame, while we draw to a different version of the screen. This is necessary so both thread aren't trying to write to the same image together, which would cause all matter of unwanted visible artifacts.

  
  #Include "BlitIMage"
  
  ScreenWidth     =GetScreenWidth()
  ScreenHeight=GetScreenHeight()
  
; Screen two FX screens
  Dim FXScreens(2)
  
  FxScreens(0)     =NewImage(ScreenWidth,ScreenHeight,2)
  FxScreens(1)     =NewImage(ScreenWidth,ScreenHeight,2)
  
  FrontScreen=0
  BackScreen=1-FrontScreen
  
  Do
     
     
     
     RenderToScreen
     // Draw the FXscreen to our PB screen in video memory
     // this BlitImage routine Mults the source image with
     // the colour, so pixels slowly fade out to black
     LockBuffer
     
     // Enable Single Job threading mode
     blitimagethreadmode 1
     
     // Call the blit function,  This job will be pushed onto
     // another cpu/core, drawing the entire image to the PB screen.
     BlitImageAlphaPostMultColour(FXScreens(frontScreen),0,0,$e0a090)
     
     UnLockBuffer
     
     
     // Switch the thread state to off.
     RenderToImage  FXScreens(backscreen)
     blitimagethreadmode 0
     
     // Draw Some circles for something to look at
     InkMode 1+64
     x=MouseX()
     y=MouseY()
     For lp =0 To 359 Step 10
        x2=x+Cos(Angle#+lp)*100
        y2=y+Sin(Angle#+lp)*100
        CircleC x2,y2,10+(lp/10),true,$213111
     Next
     
     InkMode 1
     Angle#=WrapAngle(Angle#+0.25)
     
     
     // Wait for the thread to complete it's work (if it hasn't already)
     // since we need the results of the thread
     waitonblitimagethread()
     
     // Tell PB to draw to the screen
     RenderToScreen
     
     // show the fps
     Text 10,10,"Fps:"+Str$(FPS())
     
     // Flip the Front and back screen indexes
     FrontScreen     =1-FrontScreen
     BackScreen     =1-BackScreen
     
     Sync
  Loop
  
  


      This is generally the best way to make use threading in our programs, but it can be difficult to for new programmers to identity situations where some task could be computed by the second thread while our program is doing something else.


Top







Threading Mode #2: Queued Mode


      Threading a big single task can be beneficial given the right circumstances, namely the task is big enough to warrant the expense of executing it as a thread and we can structure out program to be doing something else while it's doing it's work. But often in computer games we generally need to perform a series of render operations to create our required output. The operations individually might not be that large, but they soon stack up and really chew through cpu time if we're not careful. Sometimes we can adjust our logic to or skip certain drawing operations, but sometimes we just need to draw a lot of material. This is where BlitImageThreadMode's QUEUE mode can help out.

      Queue mode turns the Blitimage drawing commands into a stacking mode, conceptually similar to CaptureToScene. When enabled, each blitimage command request is placed on an internal stack. These operations aren't called yet though, rather they stack up until we set the BlitImageThreadMode to RENDER mode. When the render mode is set, the thread is launched and the sequence of jobs are peformed in the order they were captured.

      Example, Imagine if we called BlitImageAlphaPostMultColour a bunch of times in a row (don't know why you would) when the queue is enabled, it'd stack like this,

  
; call #0
  BlitImageAlphaPostMultColour(Screens(FrontBuffer),0,0,$e0a090)
  
; call #1
  BlitImageAlphaPostMultColour(Screens(FrontBuffer),0,0,$e0a090)
  
; call #2
  BlitImageAlphaPostMultColour(Screens(FrontBuffer),0,0,$e0a090)
  
; call #3
  BlitImageAlphaPostMultColour(Screens(FrontBuffer),0,0,$e0a090)
  
; call #4
  BlitImageAlphaPostMultColour(Screens(FrontBuffer),0,0,$e0a090)
  



      So when the thread executes, it'll call job #0, #1, #2, #3 , #4 in that order. This allows you to set up a list of brute force operations and run them externally to our game/program logic. This is really the whole idea of multiple CPU CORES, it allows us divide and conquer. The potentially this that threading can make your program twice as fast, that's assuming a best case scenario where the load can be balanced perfectly, which i'm sorry to say is largely fantasy. The reality is that you're program is bound to have an uneven load.

      For example. if we shift 5 milliseconds worth of rendering onto core #2, but core #1 only has 1 milliseconds worth of work to do before it needs to use the images core #2 is working on. Then Core #1 ends up sitting around waiting for that task to complete, and we're losing most of the benefit. The goal should be to lay out our program so we can interleave the two tasks together in such a way that they either take the same time or core #2 completes it's task long before we need the result in our program.


      Threading does introduce a few no no's, the most immediate important one would be ownership of image resources. If we set up a render task and start the thread (run it on core#2), then those the thread is using, are now as no got zone for our program, until the thread has completed it work. This is because the two CPU's are potentially writing to the same memory at the same time. This is not possible in single cpu (core) system, where you can get away with this type of logic, but it's won't work on multi core systems. You can render to the same image, you just have to make you're not drawing over, or reading from drawn over shared pixels. If you do, you'll wacky unpredictable results.


Top






How Not To Use Threading



      Successfully threading our programs rendering can often come down to how well we grasp what is actually happening behind the scenes. In PlayBASIC we've simplified the process as much as possible, but there's no getting around it. When we launch a threaded render functions onto a second cpu core, what we've really doing is asking the operating system to execute this particular piece of code on a different CPU than the main program is executing. By doing this, we're attempting to get two cpu's working on our program at once. Generally our programs only ever use a single cpu. Systems that only contain a single cpu core can still use threads, but the operating system just can't run them together. So they actually run one after the other. Which can be still give some benefit with programs with a lot of rendering work to do..

      Now since our primary threading objective is make our program share the work load between two cpu cores (when available), this means thinking not only about how we solve the problems our game design requires, but when we solve them. When you think about a program, it's really just performing a long list of operations in the order we want. Some of those operations are dependant upon previous operations and others aren't.

      Imagine we want to compute the new X position of our Hero sprite, so we'd query a sprites position, then add it's movement speed and write it's new position back. Which might look like this,

  
  Xpos=GetSpriteX(HERO)
  Xpos=Xpos+SpeedX
  PositionSpriteX HERO, Xpos
  


      While we could rearrange and condense this example down into a single line of code , the order the individual operations are being executed in, actually stays the same. As in order to compute the sprites new X coordinate, we need to know the sprites previous x coordinate. So each operation is dependant upon the previous operation. Now If we want to make threading work in our programs, then we really need to set out the program isn't dependant upon the result from a threaded jobs. If that occurs then the we're holding up our main program, because it's dependant upon the result of a previous calculation. Which is a no, no with thread

      So dependency is something of a no no with threading, what we want to do is create a solution where our threaded jobs can be running on the second core while we're off calculating something completely different. Ideally if we can split the jobs evenly we stand to gain the most performance. But what often happens is a programmer starts up a thread and then immediately needs the result, forcing the program to sit and wait for it. When this occurs, it's just dead lost performance time..

      In this example, we're wanting to use the BlitIMageClear function to clear an FX image.

  
  
; Inlude the Blit Image functions
  #Include "BlitImage"
  
  
; Create an FX image the size of the screen
  MyImage=NewFXImage(GetScreenWidth(),GetScreenHeight())
  
  
; Start of Demo loop
  Do
     
     
   ; Enable threading for single job
     blitimagethreadmode 1
     
     
   ; redirect all drawing to this image
     RenderToImage MyImage
     
   ; Push the call blit image clear onto another CPU.
   ; Since the BlitIMageClear routine is reading and
   ; writing to the MYIMAGE, it's actually acting like
   ; a threaded CLS
     BlitImageClear(MyImage,0,0,RGB(100,50,100))
     
     
   ; Set the Ink mode drawing pen to ALPHA 50 (50% blend)
     InkMode 1+32
     
     
     
   ; Make sure our threaded job has ended, before we contine
     waitonblitimagethread()
     
   ; draw a circle at the mouses current position
     Circle MouseX(),MouseY(),100
     
   ; redirect all drawing to screen back buffer
     RenderToScreen
     
   ; Render our FX image to the screen,s o we can see it
     DrawImage MyImage,0,0,false
     
   ; flip the back buffer to the front, so the user can see it
     Sync
  Loop
  
  



      Now, the core idea in is good, but the implemntation see's the BlitImageClear job pushed off onto our second core, but then almost immediately after, the program ends up sitting and waiting for it this job to end, since the following circle instruction needs thje previous job to be complete, before it can safely draw our circle. All this waiting is just dead time in your program.

      In this case there's a few solutions, the most commonly used approach is to double buffer our FX images. Double buffering just means we make two copies of our FX image. So when we call our threaded job(s) they can be off rendering to an alternative copy of the screen, while our program is drawing to the other version. This will minimize the amount of time the program is waiting, but the program is too simple for the balance to be distributed evenly.

      Here's how we might double buffer the previous example.

  
; Inlude the Blit Image functions
  #Include "BlitImage"
  
  
; Create a pair of FX images that are the same size.
  FrontImage     =NewFXImage(GetScreenWidth(),GetScreenHeight())
  BackImage     =NewFXImage(GetScreenWidth(),GetScreenHeight())
  
; Start of Demo loop
  Do
     
   ; redirect all drawing to the BACK image,
     RenderToImage BackImage
     
   ; Enable threading for single job
     blitimagethreadmode 1
     
     
   ; Push the call blit image clear onto another CPU.
   ; Since the BlitIMageClear routine is reading and
   ; writing to the same image, it's actually acting like
   ; a threaded CLS
     BlitImageClear(BackImage,0,0,RGB(100,50,100))
     
     
     
   ; redirect all drawing to the FRONT image,
     RenderToImage FrontImage
     
     
   ; Since we're not drawing to the same image, we can continue
   ; on with our drawing routine here
     
   ; Set the Ink mode drawing pen to ALPHA 50 (50% blend)
     InkMode 1+32
     
   ; draw a circle at the mouses current position
     Circle MouseX(),MouseY(),100
     
   ; redirect all drawing to the PB screen
     RenderToScreen
     
   ; Render our FX image to the screen,s o we can see it
     DrawImage FrontImage,0,0,false
     
   ; draw the frame rate
     Text 0,0,FPS()
     
   ; flip the back buffer to the front, so the user can see it
     Sync
     
     
   ; Wait for our threaded job to end, before starting the loop again
   ; just in case the operating system took a while to start our threaded
   ; job
     waitonblitimagethread()
     
   ; swap the front and back buffer images
     Swap FrontImage,BackImage
     
  Loop
  



      This time we've removed the main dependancy from our render loop by using the thread to clear the previous Fx image, while we drawing to the other one. So conceptually we're now able to clear the screen for free, at least in a perfect world. The reality is that the main programs render just isn't complex enough to ensure that our BlitIMageClear task has completed before the next frame needs to be drawn.



Top






FACTS:


      * Threading is only supported on the BlitImage commands.

      * Threading is available on all windows computers, however it's unlikely to make much difference in performance on single cored systems, or when only performing small idividual image blits. It tends to work best when the thread can draw a batch of material in one big hit

      * Some caution is needed when using threads that draws onto images in video memory, inparticular when drawing to the screen. The OS might call you thread during a refresh, creating frame tearing artifacts. There's also the potential for a crash if the OS deletes the video surface, such as when the log in screen appears.





Example:




 
Example Source: Download This Example
; Inlude the Blit Image functions
  #Include "BlitImage"
  
  
; Create an FX image the size of the screen
  Dim LightMap(2)
  LIghtMap(0)               =NewImage(GetScreenWidth(),GetScreenHeight(),2)
  LIghtMap(1)               =NewImage(GetScreenWidth(),GetScreenHeight(),2)
  
  FrontImage     =0
  BackImage     =1-FrontImage
  
  
; Create a second  FX image the size of the screen
; we'll be using to blend with our other image
  Backdrop=NewFXImage(GetScreenWidth(),GetScreenHeight())
  
; fill the backdrop with something so we can see it
  Tile=LoadNewFxImage("..\../Media/bg22.jpg")
  RenderToImage Backdrop
  TileImage Tile,0,0,false
  
  LightCol=$f08040
  
  QueueMode=true
  
; --------------------------------------------------------------
; --------------------------------------------------------------
; Start of Demo loop
; --------------------------------------------------------------
; --------------------------------------------------------------
  Do
     
   ; Set threading mode to capture.
   ; this allows us to capture the next two blit image functions to
   ; the interal 'to do' list, so both jobs can be done on the seperate thread.
     
     If QueueMode=true
        blitimagethreadmode 2
     Else
        blitimagethreadmode 0
        
     EndIf
   ; redirect all drawing to screen back buffer
     RenderToScreen
     
     ThisLIghtMap=LightMap(FrontImage)
     
   ; To do combined Blit (copy) our Image.  This
   ; version multiples the pixels in the blend
   ; image (MyImage) with the backdrop. So it's
   ; using the blend image as light map.
     BlitImageAlphaMultImage(BackDrop,0,0,ThisLightMap)
     
     
     RenderToImage ThisLightMap
     BlitImageClear(ThisLightMap,0,0,RGB(00,00,00))
     
     
     If QueueMode=true
        blitimagethreadmode 3
     EndIf
     
     RenderToScreen
     LockBuffer
     
     
   ; redirect all drawing to this image
     RenderToImage LightMap(BackImage)
     
     If LeftMouseButton()<>0 Then LightCol=RndRGB()
     
   ; draw a bunch fo circle shaped shaded polgons to represent the light
     Draw_Light(100,100,150,$445566)
     
     Draw_Light(MouseX(),MouseY(),250,LightCol)
     
     
     Draw_Light(300,200,150,$ff00000)
     
     Draw_Light(700,400,150,$445566)
     
     
     RenderToScreen
     
     UnLockBuffer
     
     
     SetCursor 0,0
     
     
   ; make pb wait until our thread queue is done.
     waitonblitimagethread()
     
   ; Display some information about what's going on
     Text 0,0 ,"Thread Queue Example (light Maps)"
     Text 0,20,"Left Mouse to change light colour"
     Text 0,40,"Threading:"+Str$(QueueMode)
     Text 0,60,FPS()
     
     FrontImage     =1-FrontIMage
     BackImage     =1-BackImage
     
   ; Press the spake to toggle threading on / off
     If SpaceKey()
        QueueMode=1-QueueMode
        FlushKeys
     EndIf
     
   ; flip the back buffer to the front, so the user can see it
     Sync
     
  Loop
  
  
  
; --------------------------------------------------------------
; --[ DRAW LIGHT ]----------------------------------------------
; --------------------------------------------------------------
  
Function Draw_Light(X#,y#,Radius,Colour=$ffffff)
  
  Colour2=RGBFade(Colour,5)
  
  Edges=12
  steps#=360.0/Edges
  InkMode 1+64
  For lp=0 To Edges-1
     Angle1#=lp*Steps#
     Angle2#=WrapAngle(Angle1#,Steps#)
     
     x1#=x#+CosRadius(angle1#,Radius)
     y1#=y#+SinRadius(angle1#,Radius)
     
     x2#=X#+CosRadius(angle2#,Radius)
     y2#=y#+SinRadius(angle2#,Radius)
     
     GouraudTri x1#,y1#,Colour2,x2#,y2#,Colour2,x#,y#,Colour
     
  Next
  InkMode 1
  
EndFunction
  
  
  
  
 
Related Info: BlitIMageThreadPriority | CpuCount | GetBlitImageThreadStatus | WaitOnBlitImageThread :
 


(c) Copyright 2002 - 2024 - Kevin Picone - PlayBASIC.com